<?php

declare(strict_types=1);

namespace Shenlink\Holiya\Lexer;

use Shenlink\Holiya\Token\Token;
use Shenlink\Holiya\Token\TokenType;

/**
 * 词法分析器
 *
 * 负责将输入的源代码字符串分解成一个个词法单元(Token)
 * 使用工厂模式和策略模式来处理不同类型的Token
 */
class Lexer
{
    /**
     * @var string 结束符
     */
    const EOF = '';

    /**
     * @var string 输入字符串
     */
    private string $input;

    /**
     * @var string 当前字符
     */
    private string $currentChar;

    /**
     * @var int 当前字符的位置
     */
    private int $position;

    /**
     * @var int 下一个字符的位置
     */
    private int $nextPosition = 0;

    /**
     * 构造函数，初始化词法分析器
     *
     * @param string $input 输入的字符串
     */
    public function __construct(string $input)
    {
        $this->input = $input;
        // 读取一个字符，并正确设置 position 和 nextPosition
        $this->readChar();
    }

    /**
     * 获取下一个词法单元
     *
     * @return Token 返回下一个Token实例
     */
    public function nextToken(): Token
    {
        $this->skipWhitespace();
        $this->skipComment();

        $handler = TokenHandlerFactory::getInstance()->getHandler($this);
        return $handler->getToken($this);
    }

    /**
     * 跳过空白字符
     *
     * @return void
     */
    public function skipWhitespace(): void
    {
        while ($this->currentChar == ' ' || $this->currentChar == "\t"
            || $this->currentChar == "\n" || $this->currentChar == "\r") {
            $this->readChar();
        }
    }

    /**
     * 跳过注释
     *
     * @return void
     */
    public function skipComment(): void
    {
        while ($this->currentChar == '/' && $this->peekChar() == '/') {
            $this->readChar();
            $this->readChar();
            while ($this->currentChar != "\n") {
                if ($this->currentChar == '') {
                    break;
                }
                $this->readChar();
            }
            $this->skipWhitespace();
        }
    }

    /**
     * 读取一个字符
     *
     * @return void
     */
    public function readChar(): void
    {
        if ($this->nextPosition >= strlen($this->input)) {
            $this->currentChar = self::EOF;
        } else {
            $this->currentChar = $this->input[$this->nextPosition];
        }
        $this->position = $this->nextPosition;
        $this->nextPosition++;
    }

    /**
     * 获取当前字符
     *
     * @return string 当前字符
     */
    public function getCurrentChar(): string
    {
        return $this->currentChar;
    }

    /**
     * 获取并前进到下一个字符
     *
     * @return string 当前字符
     */
    public function nextChar(): string
    {
        $char = $this->currentChar;
        $this->readChar();
        return $char;
    }

    /**
     * 获取下一个字符但不移动指针
     *
     * @return string 下一个字符
     */
    public function peekChar(): string
    {
        if ($this->nextPosition >= strlen($this->input)) {
            return self::EOF;
        }
        return $this->input[$this->nextPosition];
    }

    /**
     * 获取字符串 Token
     *
     * @return Token 字符串 Token
     */
    public function getStringToken(): Token
    {
        $position = $this->position + 1;
        while (true) {
            $this->readChar();
            if ($this->currentChar == self::EOF) {
                return new Token(TokenType::ILLEGAL, substr($this->input, $position, $this->position - $position));
            }
            if ($this->currentChar == '"') {
                $this->readChar();
                break;
            }
        }
        return new Token(TokenType::STRING, substr($this->input, $position, $this->position - $position - 1));
    }

    /**
     * 判断是否是字母或下划线
     *
     * @param string $char 当前字符
     * @return bool 返回判断结果，true为是，false为否
     */
    public function isLetter(string $char): bool
    {
        return $char == '_' || ($char >= 'a' && $char <= 'z') || ($char >= 'A' && $char <= 'Z');
    }

    /**
     * 判断是否是数字
     *
     * @param string $char 当前字符
     * @return bool 返回判断结果，true为是，false为否
     */
    public function isDigit(string $char): bool
    {
        return $char >= '0' && $char <= '9';
    }

    /**
     * 读取标识符
     *
     * @return string 标识符
     */
    public function readIdentifier(): string
    {
        $position = $this->position;
        // 第一个字符必须是字母或下划线
        if ($this->isLetter($this->currentChar)) {
            $this->readChar();
            // 后续字符可以是字母、下划线或数字
            while ($this->isLetter($this->currentChar) || $this->isDigit($this->currentChar)) {
                $this->readChar();
            }
        }
        return substr($this->input, $position, $this->position - $position);
    }

    /**
     * 预读取标识符，但不移动指针
     *
     * @return string 标识符
     */
    public function peekIdentifier(): string
    {
        $position = $this->position;
        $identifier = $this->readIdentifier();
        $this->position = $position;
        $this->nextPosition = $this->position + 1;
        if ($this->position >= strlen($this->input)) {
            $this->currentChar = self::EOF;
        } else {
            $this->currentChar = $this->input[$this->position];
        }
        return $identifier;
    }

    /**
     * 获取关键字 Token 或标识符 Token
     *
     * @return Token 关键字Token 或标识符 Token
     */
    public function getIdentifierToken(): Token
    {
        $identifier = $this->readIdentifier();
        $tokenType = Token::lookupIdentifier($identifier);
        return new Token($tokenType, $identifier);
    }

    /**
     * 读取数字，包含整数和浮点数，可能会遇到不合法的数字，返回数字 Token 或非法 Token
     *
     * @return Token 数字 Token 或 非法 Token
     */
    public function getNumberToken(): Token
    {
        $startPosition = $this->position;

        // 读取整数部分
        while ($this->isDigit($this->currentChar)) {
            $this->readChar();
        }

        // 如果当前字符是小数点，则可能是小数
        if ($this->currentChar === '.') {
            // 如果下一个字符不是数字，则不是小数，且是非法的 token
            if (!$this->isDigit($this->peekChar())) {
                $this->readChar();
                return new Token(TokenType::ILLEGAL, substr($this->input, $startPosition, $this->position - $startPosition));
            }
            // 读取小数点
            $this->readChar();

            // 读取小数部分
            while ($this->isDigit($this->currentChar)) {
                $this->readChar();
            }

            // 处理连续小数点的情况，如"3.4.5"或"3.4.5.6.7777"
            // 如果在读取完小数部分后又遇到小数点，则继续读取直到不是小数点或数字为止
            while ($this->currentChar === '.') {
                $this->readChar();
                // 继续读取可能的数字
                while ($this->isDigit($this->currentChar)) {
                    $this->readChar();
                }
            }
        }

        $literal = substr($this->input, $startPosition, $this->position - $startPosition);
        // 检查是否以点结尾（非法格式）
        if (strpos($literal, '.') === strlen($literal) - 1) {
            return new Token(TokenType::ILLEGAL, $literal);
        }
        // 检查是否包含多个小数点（如"3.4.5"格式）
        if (substr_count($literal, '.') > 1) {
            return new Token(TokenType::ILLEGAL, $literal);
        }
        $tokenType = (strpos($literal, '.') !== false) ? TokenType::FLOAT : TokenType::INT;
        return new Token($tokenType, $literal);
    }
}